﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace Gimela.Presentation.Controls
{
  [TemplateVisualState(Name = "Large", GroupName = "SizeStates")]
  [TemplateVisualState(Name = "Small", GroupName = "SizeStates")]
  [TemplateVisualState(Name = "Inactive", GroupName = "ActiveStates")]
  [TemplateVisualState(Name = "Active", GroupName = "ActiveStates")]
  public class ProgressRing : Control
  {
    public static readonly DependencyProperty BindableWidthProperty = DependencyProperty.Register("BindableWidth", typeof(double), typeof(ProgressRing), new PropertyMetadata(default(double), BindableWidthCallback));

    public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register("IsActive", typeof(bool), typeof(ProgressRing), new FrameworkPropertyMetadata(default(bool), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, IsActiveChanged));

    public static readonly DependencyProperty IsLargeProperty = DependencyProperty.Register("IsLarge", typeof(bool), typeof(ProgressRing), new PropertyMetadata(true, IsLargeChangedCallback));

    public static readonly DependencyProperty MaxSideLengthProperty = DependencyProperty.Register("MaxSideLength", typeof(double), typeof(ProgressRing), new PropertyMetadata(default(double)));

    public static readonly DependencyProperty EllipseDiameterProperty = DependencyProperty.Register("EllipseDiameter", typeof(double), typeof(ProgressRing), new PropertyMetadata(default(double)));

    public static readonly DependencyProperty EllipseOffsetProperty = DependencyProperty.Register("EllipseOffset", typeof(Thickness), typeof(ProgressRing), new PropertyMetadata(default(Thickness)));

    private List<Action> _deferredActions = new List<Action>();

    static ProgressRing()
    {
      DefaultStyleKeyProperty.OverrideMetadata(typeof(ProgressRing), new FrameworkPropertyMetadata(typeof(ProgressRing)));
    }

    public ProgressRing()
    {
      SizeChanged += OnSizeChanged;
    }

    public double MaxSideLength
    {
      get { return (double)GetValue(MaxSideLengthProperty); }
      private set { SetValue(MaxSideLengthProperty, value); }
    }

    public double EllipseDiameter
    {
      get { return (double)GetValue(EllipseDiameterProperty); }
      private set { SetValue(EllipseDiameterProperty, value); }
    }

    public Thickness EllipseOffset
    {
      get { return (Thickness)GetValue(EllipseOffsetProperty); }
      private set { SetValue(EllipseOffsetProperty, value); }
    }

    public double BindableWidth
    {
      get { return (double)GetValue(BindableWidthProperty); }
      private set { SetValue(BindableWidthProperty, value); }
    }

    public bool IsActive
    {
      get { return (bool)GetValue(IsActiveProperty); }
      set { SetValue(IsActiveProperty, value); }
    }

    public bool IsLarge
    {
      get { return (bool)GetValue(IsLargeProperty); }
      set { SetValue(IsLargeProperty, value); }
    }

    private static void BindableWidthCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
    {
      var ring = dependencyObject as ProgressRing;
      if (ring == null)
        return;

      var action = new Action(() =>
      {
        ring.SetEllipseDiameter(
            (double)dependencyPropertyChangedEventArgs.NewValue);
        ring.SetEllipseOffset(
            (double)dependencyPropertyChangedEventArgs.NewValue);
        ring.SetMaxSideLength(
            (double)dependencyPropertyChangedEventArgs.NewValue);
      });

      if (ring._deferredActions != null)
        ring._deferredActions.Add(action);
      else
        action();
    }

    private void SetMaxSideLength(double width)
    {
      MaxSideLength = width <= 60 ? 60.0 : width;
    }

    private void SetEllipseDiameter(double width)
    {
      if (width <= 60)
      {
        EllipseDiameter = 6.0;
      }
      else
      {
        EllipseDiameter = width * 0.1 + 6;
      }
    }

    private void SetEllipseOffset(double width)
    {
      if (width <= 60)
      {
        EllipseOffset = new Thickness(0, 24, 0, 0);
      }
      else
      {
        EllipseOffset = new Thickness(0, width * 0.4 + 24, 0, 0);
      }
    }

    private static void IsLargeChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
    {
      var ring = dependencyObject as ProgressRing;
      if (ring == null)
        return;

      ring.UpdateLargeState();
    }

    private void UpdateLargeState()
    {
      Action action;

      if (IsLarge)
        action = () => VisualStateManager.GoToState(this, "Large", true);
      else
        action = () => VisualStateManager.GoToState(this, "Small", true);

      if (_deferredActions != null)
        _deferredActions.Add(action);

      else
        action();
    }

    private void OnSizeChanged(object sender, SizeChangedEventArgs sizeChangedEventArgs)
    {
      BindableWidth = ActualWidth;
    }

    private static void IsActiveChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
    {
      var ring = dependencyObject as ProgressRing;
      if (ring == null)
        return;

      ring.UpdateActiveState();
    }

    private void UpdateActiveState()
    {
      Action action;

      if (IsActive)
        action = () => VisualStateManager.GoToState(this, "Active", true);
      else
        action = () => VisualStateManager.GoToState(this, "Inactive", true);

      if (_deferredActions != null)
        _deferredActions.Add(action);

      else
        action();
    }

    public override void OnApplyTemplate()
    {
      //make sure the states get updated
      UpdateLargeState();
      UpdateActiveState();
      base.OnApplyTemplate();
      if (_deferredActions != null)
        foreach (var action in _deferredActions)
          action();
      _deferredActions = null;
    }
  }

  internal class WidthToMaxSideLengthConverter : IValueConverter
  {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
      if (value is double)
      {
        var width = (double)value;
        return width <= 60 ? 60.0 : width;
      }

      return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
      throw new NotImplementedException();
    }
  }
}
